﻿using UnityEngine;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        public static Vector3[] GetNormalsRecalculateIfNeeded(this Mesh mesh)
        {
            if (!mesh) return new Vector3[0];
            if (!IsNullOrEmpty(mesh.normals)) return mesh.normals;
            else return GetNormalsRecalculated(mesh);
        }

        public static Vector3[] GetNormalsRecalculated(this Mesh mesh)
        {
            if (!mesh) return new Vector3[0];
            bool hasNormals = !IsNullOrEmpty(mesh.normals, mesh.vertices.Length);
            Vector3[] normals = mesh.normals;
            mesh.RecalculateNormals();
            Vector3[] newNormals = mesh.normals;
            mesh.normals = !hasNormals ? null : normals;
            return newNormals;
        }

        public static Vector3[] GetNormalsSmoothed(this Mesh mesh, float lerp = 1f)
        {
            if (!mesh) return new Vector3[0];
            var normals = mesh.GetNormalsRecalculated();
            int[] adjacentIndices;
            if (!mesh.GetAdjacentIndices(out adjacentIndices, 32)) return normals;
            var adjacencyData = new SkinnEx.AdjacencyData { indices = adjacentIndices, adjacentCount = 32, vertexCount = mesh.vertices.Length };
            int[,] smoothingIndex = adjacencyData;
            var seams = mesh.GetSeams();
            Vector3[] smoothedNormals;
            SmoothNormal(normals, out smoothedNormals, smoothingIndex, lerp, seams);
            SmoothNormal(smoothedNormals, out smoothedNormals, smoothingIndex, 0.1f);

            return smoothedNormals;
        }

        public static Vector3[] GetNormalsSmoothed(this Mesh mesh, Vector3[] normals, float lerp = 1f)
        {
            if (!mesh) return new Vector3[0];
            if(IsNullOrEmpty(normals, mesh.vertices.Length)) normals = mesh.GetNormalsRecalculated();
            int[] adjacentIndices;
            if (!mesh.GetAdjacentIndices(out adjacentIndices, 32)) return normals;
            var adjacencyData = new SkinnEx.AdjacencyData { indices = adjacentIndices, adjacentCount = 32, vertexCount = mesh.vertices.Length };
            int[,] smoothingIndex = adjacencyData;
            var seams = mesh.GetSeams();
            Vector3[] smoothedNormals;
            SmoothNormal(normals, out smoothedNormals, smoothingIndex, lerp, seams);
            SmoothNormal(smoothedNormals, out smoothedNormals, smoothingIndex, 0.1f);

            return smoothedNormals;
        }

        private static void SmoothNormal(Vector3[] source, out Vector3[] filtered, int[,] adjacentIndices,
            float lerp = 1f,
            byte[] mask = null)
        {
            filtered = new Vector3[source.Length];
            var adjacentLength = adjacentIndices.GetLength(1);
            bool useMask = !IsNullOrEmpty(mask);
            for (int i = 0; i < source.Length; i++)
            {
                if (useMask && mask[i] == 1) { filtered[i] = source[i]; continue; } 
                float x = 0f, y = 0f, z = 0f;
                var count = 0;
                for (int ii = 0; ii < adjacentLength; ii++)
                {
                    var index = adjacentIndices[i, ii]; if (index < 0) break;
                    x += source[index].x - source[i].x; y += source[index].y - source[i].y; z += source[index].z - source[i].z;
                    count++;
                }
                filtered[i].x = source[i].x + x / count; filtered[i].y = source[i].y + y / count; filtered[i].z = source[i].z + z / count;
                filtered[i] = Vector3.Lerp(source[i], filtered[i], lerp);
            }
        }
    }
}
